﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Xml;
using System.Xml.Linq;

namespace SharePointUtilitiesForDevelopers
{
    /// <summary>
    /// A domain class that represents a SharePoint list field.
    /// </summary>
    [ReadOnly(true)]    
    [DefaultProperty("DisplayName")]
    public class SharePointField
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        /// <value>The id.</value>
        [Description("The GUID that identifies the field.")]
        public string Id { get; set; }

        /// <summary>
        /// Gets or sets the source id.
        /// </summary>
        /// <value>The source id.</value>
        [Description("The namespace that defines the field or the GUID of the list in which the custom field has been created through the user interface.")]
        public string SourceId { get; set; }

        /// <summary>
        /// Gets or sets the definition.
        /// </summary>
        /// <value>The definition.</value>
        [Description("The XML definition of the field.")]
        public XElement Definition { get; set; }
        
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>The name.</value>
        [Description("The field name.")]
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets the display name.
        /// </summary>
        /// <value>The display name.</value>
        [Description("The field display name.")]
        public string DisplayName { get; set; }

        /// <summary>
        /// Gets the formatted name.
        /// </summary>
        /// <value>The formatted name.</value>
        [Description("The field name formatted for display.")]
        [Browsable(false)]
        public string FormattedName
        {
            get
            {
                return ToString();
            }
        }

        /// <summary>
        /// Gets or sets the name of the static.
        /// </summary>
        /// <value>The name of the static.</value>
        [Description("The field internal name.")]
        public string StaticName { get; set; }

        /// <summary>
        /// Gets or sets the type.
        /// </summary>
        /// <value>The type.</value>
        [Description("The field type.")]
        public SharePointFieldType FieldType { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="SharePointField"/> is hidden.
        /// </summary>
        /// <value><c>true</c> if hidden; otherwise, <c>false</c>.</value>
        [Description("Indicates if the field is hidden.")]
        public bool Hidden { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="SharePointField"/> is built-in.
        /// </summary>
        /// <value><c>true</c> if built-in; otherwise, <c>false</c>.</value>
        [Description("Indicates if the field is a built-in field.")]
        public bool BuiltIn { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="SharePointField"/> derives from a built-in type.
        /// </summary>
        /// <value><c>true</c> if derives from a built-in type; otherwise, <c>false</c>.</value>
        [Description("Indicates if the field derives from a base field type.")]
        public bool FromBaseType { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="SharePointField"/> is sealed.
        /// </summary>
        /// <value><c>true</c> if sealed; otherwise, <c>false</c>.</value>
        [Description("Indicates if other fields can be derived from the field.")]
        public bool Sealed { get; set; }

        /// <summary>
        /// Gets or sets the name of the database column where this field is stored.
        /// </summary>
        /// <value>The name of the col.</value>
        [Description("The column in the database that stores the values for this field.")]
        public string DatabaseColumnName { get; set; }

        /// <summary>
        /// Gets or sets the id of the list where the lookup item is stored.
        /// </summary>
        /// <value>The id of the lookup list.</value>
        [Description("The id of the list where the lookup item is stored if the field is a lookup.")]
        public string List { get; set; }

        /// <summary>
        /// Gets or sets the id of the list where the lookup item is stored.
        /// </summary>
        /// <value>The field from the lookup list to display.</value>
        [Description("The field from the lookup list to display if the field is a lookup.")]
        public string ShowField { get; set; }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            return string.Format("{0} ({1})", DisplayName, StaticName);
        }
    }

    /// <summary>
    /// Contains extension methods for the <see cref="SharePointField"/> class.
    /// </summary>
    public static class SharePointFieldExtensionMethods
    {
        /// <summary>
        /// Gets view fields XML definition from the list of field names.
        /// </summary>
        /// <param name="viewFieldStaticNames">The list of view field static names.</param>
        /// <returns>An <see cref="XElement"/> that represents the list of field names.</returns>
        public static XElement ToViewFieldsXElement(this IEnumerable<string> viewFieldStaticNames)
        {
            XNamespace ns = string.Empty;
            var viewFields = new XElement(ns + "ViewFields");

            // If there are fields to add
            if (viewFieldStaticNames != null)
            {
                foreach (var fieldName in viewFieldStaticNames.Distinct(StringComparer.InvariantCultureIgnoreCase))
                {
                    var fieldRefElement = new XElement(ns + "FieldRef", new XAttribute("Name", fieldName));
                    viewFields.Add(fieldRefElement);
                }
            }

            return viewFields;
        }

        /// <summary>
        /// Removes the fields not in list.
        /// </summary>
        /// <param name="viewFieldStaticNames">The view field static names.</param>
        /// <param name="fields">The fields.</param>
        /// <returns>A list of field names.</returns>
        public static IEnumerable<string> RemoveFieldsNotInList(
            this IEnumerable<string> viewFieldStaticNames,
            IEnumerable<SharePointField> fields)
        {
            if(viewFieldStaticNames == null)
            {
                return null;
            }
            if(fields == null)
            {
                return viewFieldStaticNames;
            }

            var selectedFieldNames =
                (from staticName in viewFieldStaticNames.Distinct(StringComparer.InvariantCultureIgnoreCase)
                 from field in fields
                 where
                     string.Compare(staticName, field.StaticName, StringComparison.InvariantCultureIgnoreCase) == 0
                 select field.StaticName).ToList();

            return selectedFieldNames;
        }

        /// <summary>
        /// Gets view fields XML definition from the list of field names.
        /// </summary>
        /// <param name="viewFieldStaticNames">The list of view field static names.</param>
        /// <param name="listFields">The list of available fields.</param>
        /// <returns>An <see cref="XElement"/> that represents the list of field names.</returns>
        public static XElement ToViewFieldsXElement(
            this IEnumerable<string> viewFieldStaticNames, 
            IEnumerable<SharePointField> listFields)
        {
            // If there are fields
            if (viewFieldStaticNames != null)
            {
                // Remove fields not in list
                viewFieldStaticNames = viewFieldStaticNames.RemoveFieldsNotInList(listFields);
            }

            return viewFieldStaticNames.ToViewFieldsXElement();
        }

        /// <summary>
        /// Gets view fields XML definition from the xml string.
        /// </summary>
        /// <param name="viewFieldsDefinition">The view fields definition.</param>
        /// <param name="listFields">The list of available fields.</param>
        /// <returns>An <see cref="XElement"/> that represents the list of field names.</returns>
        public static XElement ToViewFieldsXElement(
            this string viewFieldsDefinition,
            IEnumerable<SharePointField> listFields)
        {
            if (!string.IsNullOrEmpty(viewFieldsDefinition))
            {
                return XElement.Parse(viewFieldsDefinition).ToViewFieldList().RemoveFieldsNotInList(listFields).ToViewFieldsXElement();
            }
            return viewFieldsDefinition.ToViewFieldsXElement();
        }

        /// <summary>
        /// Gets view fields XML definition from the xml string.
        /// </summary>
        /// <param name="viewFieldsDefinition">The view fields definition.</param>
        /// <returns>An <see cref="XElement"/> that represents the list of field names.</returns>
        public static XElement ToViewFieldsXElement(this string viewFieldsDefinition)
        {
            XNamespace ns = string.Empty;
            var viewFields = new XElement(ns + "ViewFields");

            if (!string.IsNullOrEmpty(viewFieldsDefinition))
            {
                viewFields = XElement.Parse(viewFieldsDefinition).ToViewFieldList().ToViewFieldsXElement();
            }

            return viewFields;
        }

        /// <summary>
        /// Gets the view field XML definition as a list of field names.
        /// </summary>
        /// <param name="viewFieldsDefinition">The view fields definition.</param>
        /// <returns>An <see cref="XElement"/> that represents the list of field names.</returns>
        public static IEnumerable<string> ToViewFieldList(this XElement viewFieldsDefinition)
        {
            return (from XElement element in viewFieldsDefinition.DescendantsAndSelf()
                    where
                        String.Compare(element.Name.LocalName, "FieldRef", StringComparison.InvariantCultureIgnoreCase) == 0
                    orderby
                        SharePointXml.GetAttributeAsString(element, "Name")
                    select
                        SharePointXml.GetAttributeAsString(element, "Name")).ToList().Distinct(StringComparer.InvariantCultureIgnoreCase);
        }

        /// <summary>
        /// Gets view fields XML definition from the list of field names.
        /// </summary>
        /// <param name="viewFieldStaticNames">The list of view field static names.</param>
        /// <returns>An <see cref="XElement"/> that represents the list of field names.</returns>
        public static XmlElement ToViewFieldsXmlElement(this IEnumerable<string> viewFieldStaticNames)
        {
            return viewFieldStaticNames.Distinct(StringComparer.InvariantCultureIgnoreCase).ToViewFieldsXElement().ToXmlElement();
        }

        /// <summary>
        /// Finds the field by <see cref="SharePointField.StaticName"/>.
        /// </summary>
        /// <param name="fields">The fields.</param>
        /// <param name="staticName">Name of the static.</param>
        /// <returns>The field or null if not found.</returns>
        public static SharePointField FindByStaticName(this IEnumerable<SharePointField> fields, string staticName)
        {
            if(fields == null)
            {
                return null;
            }

            return fields.FirstOrDefault(
                f => string.Compare(f.StaticName, staticName, StringComparison.InvariantCultureIgnoreCase) == 0);
        }

        /// <summary>
        /// Gets the database column name from <see cref="SharePointField.ShowField"/> property by looking the
        /// field up in the list by static name and returning the database column name.
        /// </summary>
        /// <param name="fields">The fields.</param>
        /// <param name="showField">The show field.</param>
        /// <returns>The database column name.</returns>
        public static string GetColNameFromShowField(this IEnumerable<SharePointField> fields, string showField)
        {
            // Default to title column
            var colName = "nvarchar1";

            // If there is a ShowField value present, find that field in the list
            if (!string.IsNullOrEmpty(showField))
            {
                var field = fields.FindByStaticName(showField);
                if (field != null)
                {
                    // Get the db column name
                    colName = field.DatabaseColumnName;
                }
            }
            return colName;
        }
    }
}